home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
PC World Komputer 2010 April
/
PCWorld0410.iso
/
pluginy Firefox
/
173
/
173.xpi
/
components
/
nsGMNotifierService.js
< prev
next >
Wrap
Text File
|
2009-09-28
|
99KB
|
2,879 lines
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Gmail Notifier code.
*
* The Initial Developer of the Original Code is
* Doron Rosenberg.
* Portions created by the Initial Developer are Copyright (C) 2004 - 2006
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
const kGMSERVICE_CONTRACTID = "@mozilla.org/GMailNotifier;1"
const kGMSERVICE_CID = Components.ID("1d024ea4-5432-4831-9241-c99a85a9d2b4");
const nsINotifierService = Components.interfaces.nsIGMNotifierService
const nsINotifierProgressListener = Components.interfaces.nsIGMNotifierProgressListener;
const nsISupports = Components.interfaces.nsISupports;
const kIOSERVICE_CONTRACTID = "@mozilla.org/network/io-service;1";
const nsIIOService = Components.interfaces.nsIIOService;
const nsICookieManager = Components.interfaces.nsICookieManager;
const kCOOKIESERVICE_CONTRACTID = "@mozilla.org/cookieService;1";
const kPSWDMANAGER_CONTRACTID = "@mozilla.org/passwordmanager;1";
const nsIPasswordManagerInternal = Components.interfaces.nsIPasswordManagerInternal;
const kLOGINMANAGER_CONTRACTID = "@mozilla.org/login-manager;1"
const nsILoginManager = Components.interfaces.nsILoginManager;
const kTIMER_CONTRACTID = "@mozilla.org/timer;1";
const nsITimer = Components.interfaces.nsITimer;
const nsIHttpChannel = Components.interfaces.nsIHttpChannel;
function nsNotifierService() {
// log string
this.logString = "";
this.connectionCounter = 0;
// array of attached listeners
this.listeners = new Array();
this.notificationListenerID = null;
this.userQueue = null;
this.currentUserQueue = null;
this.loggedIn = false;
this.userList = null;
this.userCount = 0;
this.loggedInUsers = 0;
this.multiuser = false;
this.defaultUser = null;
this.connectionPhase = null;
this.isAutoConnecting = false;
this.prefBranch = null;
this.updateTimer = null;
this.timeOut = 600000; // default to 10 mins
var myThis = this;
this.PrefChangeObserver = {
observe: function(aSubject, aTopic, aData)
{
myThis.prefChanged(aData);
}
};
this.channel = null;
this.addPrefObserver("gm-notifier", this.PrefChangeObserver);
this.observerService = Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);
if (!this.supportsMultiMode()) {
this.multiuser = false;
} else {
this.multiuser = this.getPrefBranch().getBoolPref("gm-notifier.multiaccount.enabled");
}
// force logged out
this.getPrefBranch().setBoolPref("gm-notifier.loggedin", false);
// can be not set
try {
this.defaultUser = this.getPrefBranch().getCharPref("gm-notifier.users.default");
} catch (e) {}
}
// migrates old-firefox (2.0 and below) stored passwords to the new 3.0 way,
// which requres a http realm
nsNotifierService.prototype.migrateAccounts = function() {
var url = "chrome://gm-notifier/";
var passwordManager = Components.classes[kLOGINMANAGER_CONTRACTID].getService(nsILoginManager);
var logins = passwordManager.getAllLogins({});
for (var i = 0; i < logins.length; i++) {
this.logItem(" :: "+logins[i].hostname + " " + logins[i].httpRealm + " " + logins[i].username);
if (logins[i].hostname == url && (logins[i].httpRealm == null)) {
this.logItem(" ---- migrating login");
var logininfo = Components.classes["@mozilla.org/login-manager/loginInfo;1"].createInstance(Components.interfaces.nsILoginInfo);
// XXX: FF3 doesn't allow empty/null names - using " ", need to reconsider
logininfo.init(url, null, "gm-notifier", logins[i].username, logins[i].password ? logins[i].password : " ", "", "");
passwordManager.modifyLogin(logins[i], logininfo);
}
}
}
nsNotifierService.prototype.buildUserList = function() {
this.userList = new Object();
// we need to reset notifier prefs for loggedin to false. This will also
// transition 0.5.x users to the new pref way
var url = "chrome://gm-notifier/";
// check for toolkit's login manager (Mozilla 1.9)
if (Components.classes[kLOGINMANAGER_CONTRACTID]) {
var passwordManager = Components.classes[kLOGINMANAGER_CONTRACTID].getService(nsILoginManager);
var passwords = passwordManager.findLogins({}, url, null, "gm-notifier");
if (passwords.length == 0) {
// try to migrate
this.logItem("no passwords found, lets try to migrate accounts");
this.migrateAccounts();
}
passwords = passwordManager.findLogins({}, url, null, "gm-notifier");
if (passwords.length > 0) {
for (var i = 0; i < passwords.length; i++) {
username = passwords[i].username;
this.userList[username] = this.newUserListItem(username, null);
this.getPrefBranch().setBoolPref("gm-notifier.userlist." + username + ".loggedin", false);
}
}
} else {
var passwordManager = Components.classes[kPSWDMANAGER_CONTRACTID].createInstance();
if (!passwordManager) {
return;
}
passwordManager = passwordManager.QueryInterface(Components.interfaces.nsIPasswordManager);
if (!passwordManager) {
return;
}
var enumerator = passwordManager.enumerator;
while (enumerator.hasMoreElements()) {
var nextPassword;
try {
nextPassword = enumerator.getNext();
} catch(e) {
break;
}
nextPassword = nextPassword.QueryInterface(Components.interfaces.nsIPassword);
var host = nextPassword.host;
if (host == url) {
// try/catch in case decryption fails (invalid signon entry)
try {
var username = nextPassword.user;
this.userList[username] = this.newUserListItem(username, null);
this.getPrefBranch().setBoolPref("gm-notifier.userlist." + username + ".loggedin", false);
} catch (e) {
}
}
}
}
}
nsNotifierService.prototype.buildUserQueue = function() {
this.userQueue = new Array();
// add default user first, if one exists.
if (this.defaultUser) {
this.userQueue.push(this.defaultUser);
}
// not multi-user, we are done
if (!this.multiuser) {
return;
}
for (name in this.userList) {
if (name != this.defaultUser) {
// only add to the queue if autologin is set to true
var autologin = false;
try {
autologin = this.getPrefBranch().getBoolPref("gm-notifier.userlist." + name + ".autologin");
} catch (e) {}
if (autologin) {
this.userQueue.push(name);
}
}
}
}
/********** Scriptable interfaces ****************/
/**
* Initiates login
*
* @param aUsername
* @param aPassword
* @param aListenerID
*/
nsNotifierService.prototype.initLogin = function(aUsername, aPassword, aListenerID) {
this.logItem("initLogin: aListenerID is " + aListenerID);
// build user list
if (!this.userList) {
this.logItem("initLogin: no user list");
try {
this.buildUserList();
} catch (ex) {
// master password canceling throws an exception
this.userList = null;
}
}
if (!this.userList[aUsername])
this.userList[aUsername] = this.newUserListItem(aUsername, aPassword);
else {
this.userList[aUsername].password = aPassword;
}
this.notificationListenerID = aListenerID;
// user queue
if (!this.multiuser) {
// single user - one person at a time in the queue
this.userQueue = new Array();
this.userQueue.push(aUsername);
}
this.checkAccounts();
}
/**
* Initiates a new mail check
*
*/
nsNotifierService.prototype.checkNow = function() {
this.checkAccounts();
}
/**
* Logs out all users
*
*/
nsNotifierService.prototype.logout = function() {
// clear the timer
if (this.updateTimer) {
this.updateTimer.cancel();
}
this.updateTimer = null;
// clear the cookie data
this.clearCookieData();
// reset all users and log each user out
for (var username in this.userList) {
this.userList[username] = this.newUserListItem(username, null);
this.pushStateChange(username, nsINotifierProgressListener.LOGOUT_USER);
}
// clear user queue
this.userQueue = null;
this.currentUserQueue = null;
this.loggedIn = false;
this.loggedInUsers = 0;
this.pushStateChange(null, nsINotifierProgressListener.LOGOUT);
this.getPrefBranch().setBoolPref("gm-notifier.loggedin", false);
}
/**
* Logs the user out
*
*/
nsNotifierService.prototype.logoutUser = function(aUsername) {
if (!this.userList[aUsername]) {
return;
}
// remove from the user queue
if (this.userQueue) {
var index = this.isUserInQueue(aUsername);;
if (index !== false) {
this.userQueue.splice(index, 1);
}
}
if (this.userList[aUsername].state == nsINotifierService.USER_STATE_LOGGED_IN) {
this.userList[aUsername].state = nsINotifierService.USER_STATE_LOGGED_OUT;
this.loggedInUsers--;
}
// logout, so reset the userdata.
this.userList[aUsername] = this.newUserListItem(aUsername, null);
// if this was the default user and we have other logged in users, switch
// default user to first in the queue
if (this.loggedInUsers > 0) {
if (this.defaultUser == aUsername) {
this.getPrefBranch().setCharPref("gm-notifier.users.default", this.userQueue[0]);
}
}
// tell all listeners that we logged out
this.pushStateChange(aUsername, nsINotifierProgressListener.LOGOUT_USER);
if (this.loggedInUsers == 0) {
this.logout();
}
}
/**
* Logs the user in
*
*/
nsNotifierService.prototype.loginUser = function(aUsername) {
// in not in the user list, new user - such as one being added in the accounts
// dialog
if (!this.userList[aUsername]) {
var password = this.getPassword(aUsername);
// no password, no go
if (!password) {
return;
}
this.userList[aUsername] = this.newUserListItem(aUsername, password);
}
// add to queue
if (!this.userQueue) {
this.userQueue = new Array();
}
// if not in the queue, add
var userQueueIndex = this.isUserInQueue(aUsername);
if (userQueueIndex === false) {
this.userQueue.push(aUsername);
wasInQueue = false;
userQueueIndex = this.userQueue.length - 1;
}
// if already in an accounts check, add to queue
if (this.currentUserQueue != null) {
// do nothing really
} else {
// clear timer
if (this.updateTimer) {
this.updateTimer.cancel();
}
// cheat :)
this.currentUserQueue = userQueueIndex;
// force this person to be the default user if no logged in users
if (this.loggedInUsers == 0) {
this.getPrefBranch().setCharPref("gm-notifier.users.default", aUsername);
}
// presto!
this.accountCheckStart(aUsername);
}
}
nsNotifierService.prototype.isUserInQueue = function(aUsername) {
if (!this.userQueue) {
return false;
}
for (var i = 0; i < this.userQueue.length; i++) {
if (this.userQueue[i] == aUsername) {
return i;
}
}
return false;
}
/**
* Loads the user's cookies into the browser
*
*/
nsNotifierService.prototype.loadUserCookies = function(aUsername) {
if (!this.userList[aUsername] ||
this.userList[aUsername].state != nsINotifierService.USER_STATE_LOGGED_IN) {
return;
}
this.loadCookieData(aUsername);
}
nsNotifierService.prototype.loadCookieIntoApp = function(aCookieData) {
if (!aCookieData) {
return;
}
var cookieManager2 = Components.classes["@mozilla.org/cookiemanager;1"]
.getService(Components.interfaces.nsICookieManager2);
var index = aCookieData.value.indexOf("=");
var value = aCookieData.value.substr(index+1);
//this.logItem(" -- cdata data is: name: "+aCookieData.name+" value:" + value + " for "+aCookieData.domain);
// 1.9+ added a httpOnly flag
var isGecko19 = false;
if (Components.interfaces.nsIXULAppInfo) {
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"].getService(Components.interfaces.nsIXULAppInfo);
var versionChecker = Components.classes["@mozilla.org/xpcom/version-comparator;1"].getService(Components.interfaces.nsIVersionComparator);
if (versionChecker.compare(appInfo.platformVersion, "1.9") >= 0) {
isGecko19 = true;
}
}
//this.logItem(" loading cookie into app: domain:"+ aCookieData.domain+" path: "+(aCookieData.path ? aCookieData.path : "") + " name: " + aCookieData.name+ " value: " + value);
if (isGecko19) {
cookieManager2.add(aCookieData.domain, aCookieData.path ? aCookieData.path : "", aCookieData.name, value, false, false, true, Math.pow(2, 62));
} else {
cookieManager2.add(aCookieData.domain, aCookieData.path ? aCookieData.path : "", aCookieData.name, value, false, true, Math.pow(2, 62));
}
}
/**
* Adds an NotifierListener to this service
*
* @param aListener
*/
nsNotifierService.prototype.addListener = function(aListener) {
// should we autologin?
// make sure to only check for autologin the first time a listener has been
// added, ie first browser window calls us.
if ((this.listeners.length == 0) && this.getPrefBranch() &&
this.getPrefBranch().getBoolPref("gm-notifier.autologin.enabled")) {
// autologin
if (!this.userList) {
try {
this.buildUserList();
} catch (ex) {
// master password canceling throws an exception
this.userList = null;
this.listeners.push(aListener);
return;
}
}
if (this.userCount > 0) {
if (!this.defaultUser) {
// get first user in list
for (var name in this.userList) {
this.defaultUser = name;
break;
}
}
this.isAutoConnecting = true;
// user queue
if (!this.multiuser) {
// single user - one person at a time in the queue
this.userQueue = new Array();
this.userQueue.push(this.defaultUser);
}
this.checkAccounts();
}
}
this.listeners.push(aListener);
// for every new listener added, push login state
if (this.loggedIn) {
aListener.onStateChange(null, nsINotifierProgressListener.NOTIFIER_LOGGED_IN);
} else {
aListener.onStateChange(null, nsINotifierProgressListener.LOGOUT);
}
return this.listeners.length;
}
/**
* Removes an NotifierListener to this service
*
* @param aListener
*/
nsNotifierService.prototype.removeListener = function(aListener) {
var found = false;
var run = 0;
while (!found && (run < this.listeners.length)) {
if (this.listeners[run] == aListener) {
this.listeners[run] = null;
found = true;
}
run++;
}
}
/**
* Gets the reset state. Reset is set if the user clicks/checks for new mail.
*/
nsNotifierService.prototype.getResetState = function(aUsername) {
return this.userList[aUsername].resetState;
}
/**
* Sets the reset state
*
* @param aResetState
*/
nsNotifierService.prototype.setResetState = function(aUsername, aResetState) {
this.userList[aUsername].resetState = aResetState;
// need to send an event so that the unread count will update. NEW_MAIL will
// do for now, XXX: probably should have its own state.
this.pushStateChange(aUsername, nsINotifierProgressListener.NEW_MAIL);
}
nsNotifierService.prototype.getNewMailMode = function(aUsername) {
return this.userList[aUsername].newMailMode;
}
nsNotifierService.prototype.setNewMailMode = function(aUsername, aMode) {
this.userList[aUsername].newMailMode = aMode;
this.pushStateChange(aUsername, nsINotifierProgressListener.USER_MODE_CHANGED);
}
nsNotifierService.prototype.getInboxUnread = function(aUsername) {
return this.userList[aUsername].inboxUnread;
}
nsNotifierService.prototype.getUnreadCount = function() {
return this.userList[aUsername].unreadEmails;
}
nsNotifierService.prototype.getDisplayCount = function(aUsername) {
if (this.userList[aUsername].resetState) {
return 0;
} else {
if (this.getPrefBranch().getBoolPref("gm-notifier.ui.counter.showInbox")) {
return this.getInboxUnread(aUsername);
} else {
return this.userList[aUsername].unreadEmails;
}
}
}
nsNotifierService.prototype.getNewCount = function(aUsername) {
var value;
if (this.userList[aUsername].resetState) {
value = 0;
} else {
if (this.getPrefBranch().getBoolPref("gm-notifier.ui.counter.showInbox")) {
value = this.userList[aUsername].inboxNew;
} else {
value = this.userList[aUsername].newEmails;
}
}
return value;
}
nsNotifierService.prototype.getUsedMB = function(aUsername) {
return this.userList[aUsername].space_used_mb;
}
nsNotifierService.prototype.getSpaceUsed = function(aUsername) {
return this.userList[aUsername].space_used_percent;
}
nsNotifierService.prototype.getTotalSpace = function(aUsername) {
return this.userList[aUsername].total_mb;
}
nsNotifierService.prototype.getFolderCount = function(aUsername) {
return this.userList[aUsername].folders.length;
}
nsNotifierService.prototype.getFolderItem = function(aUsername, aPosition, sizeObj) {
sizeObj.value = 2;
return [this.userList[aUsername].folders[aPosition].name, this.userList[aUsername].folders[aPosition].unreadMail];
}
nsNotifierService.prototype.getActiveUserCount = function() {
return this.loggedInUsers;
}
nsNotifierService.prototype.getUserCount = function() {
// build the userlist
if (!this.userList) {
this.buildUserList();
}
return this.userCount;
}
nsNotifierService.prototype.getUserName = function(aUserNum) {
// make sure aUserNum is valid
if (aUserNum > this.userCount || aUserNum < 0) {
return;
}
var i = 0;
var username = "";
for (userName in this.userList) {
if (i == aUserNum) {
break;
}
i++;
}
return userName;
}
nsNotifierService.prototype.getUserState = function(aUsername) {
var state = nsINotifierService.USER_STATE_LOGGED_OUT;
if (this.userList[aUsername]) {
state = this.userList[aUsername].state;
}
return state;
}
nsNotifierService.prototype.removeUser = function(aUsername) {
if (!aUsername || !this.userList[aUsername]) {
return;
}
// make copy, but without the user to remove
var newList = new Array();
function copyObjectData(aObject) {
var obj = new Object();
for (item in aObject) {
if (typeof(item) == "object") {
obj[item] = copyObjectData(aObject[item]);
} else {
obj[item] = aObject[item];
}
}
return obj;
}
for (username in this.userList) {
if (username != aUsername) {
newList[username] = copyObjectData(this.userList[username]);
} else {
this.userCount--;
}
}
this.userList = newList;
}
nsNotifierService.prototype.addUser = function(aUsername) {
if (this.userList[aUsername]) {
return;
}
this.userList[aUsername] = this.newUserListItem(aUsername, null);
}
nsNotifierService.prototype.getQueueCount = function() {
if (!this.userQueue) {
this.buildUserQueue();
}
return this.userQueue.length;
}
nsNotifierService.prototype.getQueueUserName = function(aUserNum) {
// make sure aUserNum is valid
if (aUserNum > this.userCount || aUserNum < 0) {
return null;
}
return this.userQueue[aUserNum];
}
nsNotifierService.prototype.setTimeout = function(aMinutes) {
// convert to ms
this.timeOut = (aMinutes * 60000);
// set timer only if we are logged in
if (this.loggedIn) {
this.setTimer();
}
}
/**
* Adds an item to the log
*
*/
nsNotifierService.prototype.logItem = function(aLogString) {
this.logString += aLogString + "|||";
}
nsNotifierService.prototype.getLog = function() {
return this.logString;
}
nsNotifierService.prototype.clearLog = function() {
this.logString = "";
}
/*************** Private Methods ************/
nsNotifierService.prototype.talkToServer = function(aUsername, aURL, aPostData,
aCookieData, aReferrer,
aCallbackFunc) {
this.logItem(" talkToServer Called ("+aUsername+") with url: "+aURL);
//dump("\n\nurl: " + aURL);
// the IO service
var ioService = Components.classes[kIOSERVICE_CONTRACTID].getService(nsIIOService);
// create an nsIURI
var uri = ioService.newURI(aURL, null, null);
//nsIInputStream
var uploadStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
.createInstance(Components.interfaces.nsIStringInputStream);
if (aPostData) {
this.logItem(" -- post data is: " + aPostData);
uploadStream.setData(aPostData, aPostData.length);
}
// clean up if old channel exists. Should never happen really!
if (this.channel) {
this.channel.cancel(Components.results.NS_BINDING_ABORTED);
this.channel = null;
}
// get a channel for that nsIURI
this.channel = ioService.newChannelFromURI(uri);
// get a httpchannel and make it a post
var httpChannel = this.channel.QueryInterface(nsIHttpChannel);
// set a referrer
if (aReferrer) {
var referrerUri = ioService.newURI(aReferrer, null, null);
httpChannel.referrer = referrerUri;
}
if (aPostData) {
var uploadChannel = this.channel.QueryInterface(Components.interfaces.nsIUploadChannel);
uploadChannel.setUploadStream(uploadStream, "application/x-www-form-urlencoded", -1);
// order important - setUploadStream resets to get/put
httpChannel.requestMethod = "POST";
}
if (aCookieData){
// httpChannel.setRequestHeader("Cookie", aCookieData, false);
this.logItem(" -- cookie data is: " + JSON.toString(aCookieData));
for (var run = 0; run < aCookieData.length; run++) {
httpChannel.setRequestHeader("Cookie", aCookieData[run], true);
}
}
var observer = new this.observer(aCallbackFunc, aUsername, this);
this.channel.notificationCallbacks = observer;
this.channel.asyncOpen(observer, null);
}
nsNotifierService.prototype.checkAccounts = function() {
// checks accounts in the userqueue
if (!this.userQueue) {
this.buildUserQueue();
}
this.currentUserQueue = 0;
this.pushStateChange(null, nsINotifierProgressListener.ACCOUNTS_CHECK_INITIATED);
this.accountCheckStart(this.userQueue[0]);
}
nsNotifierService.prototype.accountCheckResult = function(aUsername, aStatus) {
if (aStatus == nsINotifierProgressListener.LOGIN_DETAILS_INVALID) {
this.logItem(" Login details were invalid");
this.userList[aUsername].state = nsINotifierService.USER_STATE_INVALID_DETAILS;
if (this.userList[aUsername].state != nsINotifierService.USER_STATE_INVALID_DETAILS) {
if (this.userList[aUsername].state == nsINotifierService.USER_STATE_LOGGED_IN) {
// if was logged in, remove from logged in user count
this.loggedInUsers--;
this.clearCookieObject(aUsername);
this.getPrefBranch().setBoolPref("gm-notifier.userlist." + aUsername + ".loggedin", false);
}
}
} else if (aStatus == nsINotifierProgressListener.LOGIN_FAILED) {
this.logItem(" Login connection failed");
this.userList[aUsername].state = nsINotifierService.USER_STATE_LOGGED_OUT;
// if user was logged in, log him out.
if (this.userList[aUsername].state == nsINotifierService.USER_STATE_LOGGED_IN) {
this.loggedInUsers--;
this.clearCookieObject(aUsername);
this.getPrefBranch().setBoolPref("gm-notifier.userlist." + aUsername + ".loggedin", false);
}
} else if (aStatus == nsINotifierProgressListener.LOGIN_SUCCESS) {
// if user was logged out, log him in.
if (this.userList[aUsername].state == nsINotifierService.USER_STATE_LOGGED_OUT) {
this.loggedInUsers++;
this.getPrefBranch().setBoolPref("gm-notifier.userlist." + aUsername + ".loggedin", true);
}
this.userList[aUsername].state = nsINotifierService.USER_STATE_LOGGED_IN;
}
// if failed to login, check for default user
if (this.multiuser && aStatus != nsINotifierProgressListener.LOGIN_SUCCESS) {
if (this.defaultUser == aUsername) {
// if the default user didn't log in, we switch the default user out.
if (this.loggedInUsers > 0) {
// we have a user logged in already
for (name in this.userQueue) {
if (this.userList[aUsername].state == nsINotifierService.USER_STATE_LOGGED_IN) {
this.getPrefBranch().setCharPref("gm-notifier.users.default", name);
break;
}
}
} else {
// use the next person, if one exists
var name = this.userQueue[this.currentUserQueue+1];
if (name) {
this.getPrefBranch().setCharPref("gm-notifier.users.default", name);
}
}
}
} else if (!this.defaultUser && aStatus == nsINotifierProgressListener.LOGIN_SUCCESS) {
// no default user, make this person it!
this.getPrefBranch().setCharPref("gm-notifier.users.default", aUsername);
}
this.pushStateChange(aUsername, aStatus);
}
nsNotifierService.prototype.accountCheckStart = function(aUsername) {
// only log 2 sessions. Helps reduce memory usage.
if (this.connectionCounter >= 2) {
this.logString = "";
this.connectionCounter = 0;
}
// http observer - init
if (this.supportsMultiMode()) {
this.observerService.addObserver(this, "http-on-modify-request", false);
this.observerService.addObserver(this, "http-on-examine-response", false);
}
this.startConnection(aUsername);
}
nsNotifierService.prototype.accountCheckComplete = function() {
// http observer - cleanup
if (this.supportsMultiMode()) {
this.observerService.removeObserver(this, "http-on-modify-request");
this.observerService.removeObserver(this, "http-on-examine-response");
}
this.channel = null;
this.currentUserQueue++;
if (this.currentUserQueue < this.userQueue.length) {
this.accountCheckStart(this.userQueue[this.currentUserQueue]);
} else {
// no longer auto connecting
this.isAutoConnecting = false;
this.currentUserQueue = null;
// increment the connection counter
this.connectionCounter++;
this.pushStateChange(null, nsINotifierProgressListener.ACCOUNTS_CHECK_COMPLETED);
this.setTimeout(this.getPrefBranch().getIntPref("gm-notifier.update.interval"));
}
}
/**
* startConnection and callback are the Gmail specific parts.
*/
var gConnectionPhases = {start: 1, gmail1phase1: 2, gmail2phase1: 3, finish: 4, finish2: 5};
nsNotifierService.prototype.startConnection = function (aUsername) {
this.logItem(" StartConnection: Init");
if (!aUsername) {
return;
}
var start_time = (new Date()).getTime();
this.logItem(" -- Username ("+aUsername+") found ");
if (!this.userList[aUsername]) {
this.logItem(" -- Uhoh, userlist didn't have user, create one");
this.userList[aUsername] = this.newUserListItem(aUsername, null);
}
this.userList[aUsername].resetState = false;
this.reusingCookie = false;
this.connectionPhase = gConnectionPhases.start;
this.pushStateChange(aUsername, nsINotifierProgressListener.LOGIN_INITIATED);
var urlprotocol = "https://";//this.getURLProtocol();
var hasHostedDomain = this.isHostedDomain(aUsername);
// if we have stored cookie data, go directly to last phase
if (this.userList[aUsername].cookieObject.GX) {
this.reusingCookie = true;
this.logItem(" -- attempting quick connect");
if (this.userList[aUsername].isGmail20) {
this.connectionPhase = gConnectionPhases.finish2;
var url = urlprotocol + "mail.google.com/mail/?ik="+this.userList[aUsername].idkey+"&view=tl&start=0&num=25&rt=h&q=is%3Aunread&search=query";
this.talkToServer(aUsername, url,
null, null, urlprotocol + "mail.google.com/",
this.hitch(this, this.callback));
} else {
this.connectionPhase = gConnectionPhases.finish;
this.talkToServer(aUsername, urlprotocol+ "mail.google.com/mail/?search=query&q=is%3Aunread&view=tl&start=0&init=1&ui=1",
null, null, urlprotocol + "mail.google.com/",
this.hitch(this, this.callback));
}
} else if (hasHostedDomain && (this.userList[aUsername].cookieObject.GXAS || this.userList[aUsername].cookieObject.GXAS_SEC)) {
this.reusingCookie = true;
this.logItem(" -- attempting hosted quick connect");
var hosteddomain = this.userList[aUsername].hosteddomain;
if (this.userList[aUsername].isGmail20) {
this.connectionPhase = gConnectionPhases.finish2;
// XXX: here also had to hardcode http
this.talkToServer(aUsername, "http://mail.google.com/a/"+hosteddomain+"/?ik="+this.userList[aUsername].idkey+"&view=tl&start=0&num=25&rt=h&q=is%3Aunread&search=query",
null, null, urlprotocol + "mail.google.com/a/"+hosteddomain,
this.hitch(this, this.callback));
} else {
this.connectionPhase = gConnectionPhases.finish;
this.talkToServer(aUsername, urlprotocol+ "mail.google.com/a/"+hosteddomain+"/?search=query&q=is%3Aunread&view=tl&start=0&init=1&ui=1",
null, null, urlprotocol + "mail.google.com/a/"+hosteddomain,
this.hitch(this, this.callback));
}
} else {
// set GMAIL_LOGIN
/*var now = (new Date()).getTime();
var cookie = "T" + start_time + "/" + start_time + "/" + now;
var d = new Date();
var expr = new Date(d.getFullYear()+1);
this.userList[aUsername].cookieObject.GMAIL_LOGIN = "GMAIL_LOGIN="+cookie;*/
if (hasHostedDomain) {
this.exploritorySurgeryHosted(aUsername);
} else {
this.exploritorySurgery(aUsername);
}
}
}
nsNotifierService.prototype.exploritorySurgery = function(aUsername) {
// load in the main gmail page and see what it tells us
this.logItem(" exploritorySurgery for "+aUsername);
this.talkToServer(aUsername, "https://www.google.com/accounts/ServiceLogin?service=mail", "", null, "https://www.google.com", this.hitch(this, this.exploritorySurgeryCallback));
}
nsNotifierService.prototype.exploritorySurgeryHosted = function(aUsername) {
// load in the main gmail page and see what it tells us
this.logItem(" exploritorySurgery for hosted account: "+aUsername);
var index = aUsername.indexOf("@");
var realusername = aUsername.substring(0, index);
var hosteddomain = aUsername.substring(index + 1, aUsername.length);
this.userList[aUsername].hosteddomain = hosteddomain;
this.logItem(" -- hosted domain found: " + hosteddomain);
this.talkToServer(aUsername, "https://www.google.com/a/"+this.getHostedDomain(aUsername)+"/ServiceLogin?service=mail", "", null, "https://www.google.com", this.hitch(this, this.exploritorySurgeryHostedCallback));
}
nsNotifierService.prototype.exploritorySurgeryCallback = function(aData, aRequest, aUsername) {
var val = aData.indexOf('<input type="hidden" name="service"');
if (val >= 0) {
var data = {}
var forms = aData.match(/id="gaia_loginform"((.|[\n\r\s])*?)<\/form>/i);
if (forms) {
var inputs = forms[1].match(new RegExp('<input(.*)[^>]*>', "gi"));
for (var i = 0; i < inputs.length; i++) {
var name = inputs[i].match(new RegExp('name="([^".]*)"', "i"));
var value = inputs[i].match(new RegExp('value="(.*)"', "i"));
if (name && value) {
data[name[1]] = value[1];
}
}
}
this.phaseStart(aUsername, data);
} else {
// go to phase 1
this.phaseStart(aUsername);
}
}
nsNotifierService.prototype.exploritorySurgeryHostedCallback = function(aData, aRequest, aUsername) {
var val = aData.indexOf('<input type="hidden" name="service"');
if (val >= 0) {
var data = {}
var forms = aData.match(/id="gaia_loginform"((.|[\n\r\s])*?)<\/form>/i);
if (forms) {
var inputs = forms[1].match(new RegExp('<input(.*)[^>]*>', "gi"));
for (var i = 0; i < inputs.length; i++) {
var name = inputs[i].match(new RegExp('name="([^".]*)"', "i"));
var value = inputs[i].match(new RegExp('value="(.*)"', "i"));
if (name && value) {
data[name[1]] = value[1];
}
}
}
this.phaseStartHosted(aUsername, data);
} else {
// go to phase 1
this.phaseStartHosted(aUsername);
}
}
nsNotifierService.prototype.phaseStart = function(aUsername, aInputs) {
var data = "";
if (aInputs) {
aInputs["Email"] = aUsername;
aInputs["Passwd"] = this.getPassword(aUsername);
aInputs["continue"] = "http://mail.google.com/mail/?ui=1&zy=l"
var c = 0;
for (var label in aInputs) {
data += (c == 0 ? "?": "&")+label + "=" + encodeURIComponent(aInputs[label]);
c++;
}
} else {
data = "?service=mail&Email=" + encodeURIComponent(aUsername)
+ "&Passwd=" + encodeURIComponent(this.getPassword(aUsername)) +
"&rm=false&null=Sign%20in&continue=https://mail.google.com/mail?nsr=1&ui=html&zy=l";
}
this.talkToServer(aUsername, "https://www.google.com/accounts/ServiceLoginAuth?service=mail", data,
null, "https://www.google.com/accounts/ServiceLoginAuth", this.hitch(this, this.callback));
}
nsNotifierService.prototype.phaseStartHosted = function(aUsername, aInputs) {
var data = "";
var index = aUsername.indexOf("@");
var realusername = aUsername.substring(0, index);
var hosteddomain = aUsername.substring(index + 1, aUsername.length);
if (aInputs) {
aInputs["Email"] = realusername;
aInputs["Passwd"] = (this.getPassword(aUsername));
aInputs["continue"] = "https://mail.google.com/a/"+hosteddomain+"/";
var c = 0;
for (var label in aInputs) {
data += (c == 0 ? "?": "&")+label + "=" + encodeURIComponent(aInputs[label]);
c++;
}
} else {
data = "at=null&continue="+encodeURIComponent("https://mail.google.com/a/"+hosteddomain+"/")+"&service=mail&Email=" + escape(realusername)
+ "&Passwd=" + encodeURIComponent(this.getPassword(aUsername));
}
this.talkToServer(aUsername, "https://www.google.com/a/"+hosteddomain+"/LoginAction2?service=mail", data,
null, "https://www.google.com/accounts/ServiceLoginAuth", this.hitch(this, this.callback));
}
/*
Phase Description
0 Before anything has happend
1 First connection
...
*/
nsNotifierService.prototype.callback = function(aData, aRequest, aUsername) {
this.logItem(" callback called for ("+aUsername+"), phase" + this.connectionPhase);
this.logItem(" -- data ("+this.connectionPhase+")");
//dump("\n\n\n----------\n"+aData+"\n----------\n\n\n");
this.processConnectionPhase(aData, aRequest, aUsername);
}
nsNotifierService.prototype.processConnectionPhase = function(aData, aRequest, aUsername) {
var urlprotocol = this.getURLProtocol();
switch (this.connectionPhase) {
case gConnectionPhases.start:
var cookieData;
// if no cookies sent, an exception is thrown
/* try{
var httpChannel = aRequest.QueryInterface(Components.interfaces.nsIHttpChannel)
cookieData = httpChannel.getRequestHeader("cookie");
} catch(e){} */
var val = aData.indexOf("location.replace");
var gmail20 = false;
// gmail 2.0 ?
if (val < 0) {
// 2.0 has a meta redirect
// XXX: what about hosted!!!
val = aData.indexOf("URL=http://mail.google.com/mail");
if (val < 0) {
// old gmail 2.0
val = aData.indexOf("MainPage.CheckLoaded();");
}
// newer way ?
if (val < 0) {
val = aData.indexOf("function onLoadTimeout()");
}
if (val > 0) {
gmail20 = true;
this.userList[aUsername].isGmail20 = true;
this.logItem(" -- gmail 2.0 detected");
}
if (val < 0 && this.isHostedDomain(aUsername)) {
// hosted gmail 2.0?
val = aData.indexOf("ID_KEY:");
if (val > 0) {
this.logItem(" -- hosted gmail 2.0 detected");
this.userList[aUsername].isGmail20 = true;
gmail20 = true;
} else {
// last ditch attempts
// look for <link rel="alternate" type="application/atom+xml"
val = aData.indexOf('<link rel="alternate" type="application/atom+xml"');
if (val > 0) {
this.logItem(" -- hosted gmail 2.0 detected");
this.userList[aUsername].isGmail20 = true;
gmail20 = true;
}
}
}
if (val < 0) {
this.userList[aUsername].isGmail20 = false;
}
}
if (val < 0) {
// XXX - check if its an image request
// check if it is a password re-request
if (aData.indexOf("<form action=\"LoginAuth\"") >= 0 ||
aData.indexOf("errormsg_0_Passwd") >= 0) {
this.logItem(encodeURIComponent(aData));
this.accountCheckResult(aUsername, nsINotifierProgressListener.LOGIN_DETAILS_INVALID);
} else {
this.accountCheckResult(aUsername, nsINotifierProgressListener.LOGIN_FAILED);
}
this.accountCheckComplete();
return;
}
var url = urlprotocol;
if (this.userList[aUsername].hosteddomain) {
url += "mail.google.com/hosted/" + this.userList[aUsername].hosteddomain;
} else {
url += "mail.google.com/mail";
}
if (gmail20) {
if (this.isHostedDomain(aUsername)) {
// we can skip to the next phase here, as ID key is present already
// for hosted gmail 2.0 accounts
this.connectionPhase = gConnectionPhases.gmail2phase1;
this.processConnectionPhase(aData, aRequest, aUsername);
} else {
var regurl = new RegExp('URL=(.*)"').exec(aData);
if (regurl && regurl[1]) {
url = regurl[1];
} else {
}
// XXX: hack - need to only load cookies for the domain!
delete this.userList[aUsername].cookieObject.GAUSR;
this.connectionPhase = gConnectionPhases.gmail2phase1;
this.talkToServer(aUsername, url, null, null, url+"/",
this.hitch(this, this.callback));
}
} else {
//var match = aData.match(/location.replace\((.*)\)/gm);
//url = match[0].substr(18, match[0].length-2-18);
this.connectionPhase = gConnectionPhases.gmail1phase1;
this.talkToServer(aUsername, url + "?ui", null, null, url+"/",
this.hitch(this, this.callback));
}
break;
case gConnectionPhases.gmail1phase1:
var url = urlprotocol;
if (this.userList[aUsername].hosteddomain) {
url += "mail.google.com/hosted/" + this.userList[aUsername].hosteddomain;
} else {
url += "mail.google.com/mail";
}
this.connectionPhase = gConnectionPhases.finish;
this.logItem("final url: "+url);
this.talkToServer(aUsername,
url + "/?search=query&q=is%3Aunread&view=tl&start=0&init=1&ui=1",
null, null, url, this.hitch(this, this.callback));
break;
case gConnectionPhases.gmail2phase1:
this.logItem(" looking for idkey");
// figure out the id key
var result = new RegExp("ID_KEY = \'([a-zA-Z0-9]*)\'").exec(aData);
if (!result) {
// can also be: ID_KEY:'...'
result = new RegExp("ID_KEY:\'([a-zA-Z0-9]*)\'").exec(aData);
}
if (!result) {
// can also be: ID_KEY:"..."
result = new RegExp("ID_KEY:\"([a-zA-Z0-9]*)\"").exec(aData);
var newHostedType = true;
}
var url = urlprotocol;
if (this.userList[aUsername].hosteddomain) {
if (!newHostedType) {
// XXX: blah, need to hard code http here for some reason - but not for
//"new" hosted accounts
url = "http://mail.google.com/a/" + this.userList[aUsername].hosteddomain+"/";
} else {
url += "mail.google.com/a/" + this.userList[aUsername].hosteddomain+"/";
}
} else {
url += "mail.google.com/mail";
}
// failed to login
if (!result) {
if (this.reusingCookie) {
this.logItem(" - Login failed, but were reusing cookie data, so trying afresh.");
// if we failed but were reusing the cookie, clear it and restart
this.clearCookieObject(aUsername);
this.reusingCookie = false;
this.startConnection(aUsername);
break;
} else {
// if non en-US, we need to switch to gmail 1.0
if (aData.indexOf('(js.location.replace("?ui=1' > 0)) {
this.userList[aUsername].isGmail20 = false;
this.logItem(" - Not Gmail 2.0 after all - probably non-US locale.");
this.connectionPhase = gConnectionPhases.finish;
this.talkToServer(aUsername,
url + "/?search=query&q=is%3Aunread&view=tl&start=0&init=1&ui=1",
null, null, url, this.hitch(this, this.callback));
break;
} else {
this.logItem(" - Login failed, page not the correct one.");
this.accountCheckResult(aUsername, nsINotifierProgressListener.LOGIN_FAILED);
this.accountCheckComplete();
break;
}
}
}
var idkey = result[1];
this.userList[aUsername].idkey = idkey;
url += "?ik="+idkey+"&view=tl&start=0&num=25&rt=h&q=is%3Aunread&search=query";
this.connectionPhase = gConnectionPhases.finish2;
this.talkToServer(aUsername, url, null, null, url+"/", this.hitch(this, this.callback));
break;
case gConnectionPhases.finish2:
// gmail 2.0 finish
this.logItem(" connecting to gmail 2.0 complete.");
// check if right page - has to have a ti
if (!aData.match(/\["ti",.*\n.*\]/gm)) {
// failed to login
if (this.reusingCookie) {
this.logItem(" - Login failed, but were reusing cookie data, so trying afresh.");
// if we failed but were reusing the cookie, clear it and restart
this.clearCookieObject(aUsername);
this.reusingCookie = false;
this.startConnection(aUsername);
} else {
this.logItem(" - Login failed, final page not the correct one.");
//this.logItem(aData);
this.accountCheckResult(aUsername, nsINotifierProgressListener.LOGIN_FAILED);
this.accountCheckComplete();
}
return;
}
// account check was successfull
this.accountCheckSuccess(aUsername);
var obj = {totalunread: 0, inboxunread: 0, labels: [], quota: {}};
// handle the total unread
// example: D(["ti","Search results for: is:unread",900,1,3000,"is:unread",_A(),"621a",700]);
var ti = aData.match(/\["ti",.*\n.*\]/gm);
this.logItem(" - ti is " + ti);
var arr = ti[0].split(",");
obj.totalunread = parseInt(arr[2], 10);
this.logItem(" - total unread count is " + obj.totalunread);
// now handle inbox and labels, which are stored in ld
var ld = aData.match(/\["ld",.*\n(.*]\n)+\]/gm);
this.logItem(" - ld is " + ld.length);
// setup a sandbox for eval usage
var s = Components.utils.Sandbox("about:blank");
// ld has three parts = "ld", general data (inbox, span, etc) and finally labels
var data = Components.utils.evalInSandbox(ld[0], s);
// need the ^i entry, which is the first, and the unread count is in the 2nd position
obj.inboxunread = data[1][0][1];
// now handle labels
var labels = data[2];
// utf-8 fun
var conv = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
for (var i = 0; i < labels.length; i++) {
var name = escape(conv.convertStringToUTF8(labels[i][0], "utf-8", false));
var unread = labels[i][1];
obj.labels.push({name: name, unread: unread});
}
// now handle quota
var qu = aData.match(/\["qu",.*\]/);
myArray = Components.utils.evalInSandbox(qu[0], s);
obj.quota.spaceused = myArray[1];
obj.quota.totalmb = myArray[2];
obj.quota.spaceusedpercent = myArray[3];
this.updateUserList(aUsername, obj);
this.accountCheckComplete();
break;
case gConnectionPhases.finish:
this.logItem(" connecting to gmail complete.");
// Gmail now serves 2 versions of the ds string:
// D(["ds",46,0,0,0,0,1298,0] or
// D(["ds",[["inbox",3]\n,["drafts",2]\n,["spam",1053]\n]\n]n);
var val;
var newDS = false;
if (aData.match(/\["ds",\[\[/)) {
// new way
this.logItem(" -- new ds");
val = aData.match(/\["ds",.*\n(.*]\n)+\]/gm);
newDS = true;
} else {
val = aData.match(/\["ds",.*\]/);
this.logItem(" -- old ds");
}
if (!val) {
// failed to login
if (this.reusingCookie) {
this.logItem(" - Login failed, but were reusing cookie data, so trying afresh.");
// if we failed but were reusing the cookie, clear it and restart
this.clearCookieObject(aUsername);
this.reusingCookie = false;
this.startConnection(aUsername);
} else {
this.logItem(" - Login failed, final page not the correct one.");
//this.logItem(aData);
this.accountCheckResult(aUsername, nsINotifierProgressListener.LOGIN_FAILED);
this.accountCheckComplete();
}
return;
}
if (!this.multiuser) {
// if we are in single user mode, set the user to be the default
var defaultUser = this.getPrefBranch().getCharPref("gm-notifier.users.default");
if (defaultUser != aUsername) {
this.getPrefBranch().setCharPref("gm-notifier.users.default", aUsername);
}
}
// if we aren't logged in, we are now and tell the world
if (!this.loggedIn) {
// update the pref
this.getPrefBranch().setBoolPref("gm-notifier.loggedin", true);
this.loggedIn = true;
this.pushStateChange(null, nsINotifierProgressListener.NOTIFIER_LOGGED_IN);
}
// success!
this.accountCheckResult(aUsername, nsINotifierProgressListener.LOGIN_SUCCESS);
// get the unread count
var ts = aData.match(/\["ts",.*\]/gm);
var myArray = (JSON.fromString(ts[0]));
this.logItem(" - ts is " + ts);
var totalUnread = myArray[3];
// handle inbox
myArray = JSON.fromString(val[0]);
this.logItem(" - val[0] " + val[0]);
var inboxUnread = 0;
if (newDS) {
inboxUnread = myArray[1][0][1];
} else {
inboxUnread = myArray[1];
}
this.logItem(" - inboxUnread " + inboxUnread + " and this.userList[aUsername].inboxUnread is " + this.userList[aUsername].inboxUnread);
this.userList[aUsername].inboxNew = inboxUnread - this.userList[aUsername].inboxUnread;
this.userList[aUsername].inboxUnread = inboxUnread;
// clear the array
this.userList[aUsername].folders = new Array();
this.userList[aUsername].folders.push(this.createFolder("inbox", inboxUnread));
// XXX: do we really need this?
if (this.userList[aUsername].inboxNew < 0) {
this.userList[aUsername].inboxNew = 0;
}
// utf-8 fun
var conv = Components.classes["@mozilla.org/intl/utf8converterservice;1"].getService(Components.interfaces.nsIUTF8ConverterService);
// handle labels
var labelsVal = aData.match(/\["ct",.*\n(.*]\n)+\]/gm);
if (labelsVal) {
var labelsArray = JSON.fromString(labelsVal[0])[1];
for (var run = 0; run < labelsArray.length; run++) {
var folderName = escape(conv.convertStringToUTF8(labelsArray[run][0], "utf-8", false));
// gmail skins extension workaround
var skip = (folderName.indexOf("gmskin%3A") == 0);
if (!skip) {
this.userList[aUsername].folders.push(this.createFolder(folderName, labelsArray[run][1]));
this.logItem(" - Folder found (name: " + folderName + " and unread emails: " + labelsArray[run][1] + ")");
}
}
}
// new/unread mail check
this.logItem(" -inboxUnread: " + inboxUnread + ", unreadEmails: " + this.userList[aUsername].unreadEmails + ", inboxNew: "+this.userList[aUsername].inboxNew+", totalunread: " + totalUnread);
var showInboxOnly = this.getPrefBranch().getBoolPref("gm-notifier.ui.counter.showInbox");
var hasNewMail = (totalUnread > this.userList[aUsername].unreadEmails);
if (!hasNewMail && showInboxOnly) {
hasNewMail = (this.userList[aUsername].inboxNew > 0);
}
if ((this.userList[aUsername].unreadEmails != null) && hasNewMail) {
this.logItem(" New Unread Mail Found!");
// new email
this.userList[aUsername].newEmails = totalUnread - this.userList[aUsername].unreadEmails;
this.userList[aUsername].unreadEmails = totalUnread;
if (showInboxOnly && (this.userList[aUsername].inboxNew == 0)) {
// show inbox only, but new mail wasn't in inbox
this.pushStateChange(aUsername, nsINotifierProgressListener.NO_NEW_MAIL);
//this.setNewMailMode(aUsername, false);
} else {
this.pushStateChange(aUsername, nsINotifierProgressListener.NEW_MAIL);
this.setNewMailMode(aUsername, true);
var count = this.getNewCount(aUsername);
this.newMailNotification(aUsername, count);
this.newMailCount = count;
}
} else if (this.userList[aUsername].unreadEmails == null && totalUnread > 0) {
// first time
this.logItem(" New Mail Found!");
this.userList[aUsername].newEmails = totalUnread;
this.userList[aUsername].unreadEmails = totalUnread;
if (showInboxOnly && (this.userList[aUsername].inboxNew == 0)) {
// show inbox only, but new mail wasn't in inbox
this.pushStateChange(aUsername, nsINotifierProgressListener.NO_NEW_MAIL);
} else {
this.pushStateChange(aUsername, nsINotifierProgressListener.NEW_MAIL);
this.setNewMailMode(aUsername, true);
this.logItem(" - amount of new mail is " + this.userList[aUsername].newEmails);
var count = this.getDisplayCount(aUsername);
this.logItem(" - mail notification about to be set, new mail count is " + count);
this.newMailNotification(aUsername, count);
this.newMailCount = count;
}
} else {
// check if the count decreased
var decreased = false;
if (totalUnread < this.userList[aUsername].unreadEmails) {
decreased = true;
}
this.userList[aUsername].unreadEmails = totalUnread;
this.logItem(" No New Unread Mail Found!");
this.pushStateChange(aUsername, nsINotifierProgressListener.NO_NEW_MAIL);
// only set mode to false if the count decreased. So if the user hasn't
// checked the mail and we had new mail before, we stay in the new mail
// mode.
if (decreased) {
this.userList[aUsername].newEmails = 0;
this.setNewMailMode(aUsername, false);
} else if (this.getNewMailMode(aUsername) &&
this.getPrefBranch().getBoolPref("gm-notifier.notification.repeat")) {
this.logItem(" - mail notification about to be set again, new mail count is " + this.newMailCount);
this.newMailNotification(aUsername, this.newMailCount);
} else {
this.userList[aUsername].newEmails = 0;
}
}
val = aData.match(/\["qu",.*\]/);
myArray = JSON.fromString(val[0]);
var spaceused, totalmb, spaceusedpercent;
spaceused = myArray[1];
totalmb = myArray[2];
spaceusedpercent = myArray[3];
// if we have %, parseInt time. This happens for localized gmail right
// now
if (spaceusedpercent.indexOf("%") != -1) {
function _parseInt(aString) {
var parsed = parseInt(aString, 10);
if (parsed != NaN) {
return parsed;
}
return aString;
}
spaceused = _parseInt(spaceused);
totalmb = _parseInt(totalmb);
spaceusedpercent = _parseInt(spaceusedpercent);
}
this.userList[aUsername].space_used_mb = spaceused;
this.userList[aUsername].total_mb =totalmb;
this.userList[aUsername].space_used_percent = spaceusedpercent;
this.accountCheckComplete();
//this.setTimer();
break;
}
}
nsNotifierService.prototype.updateUserList = function(aUsername, aData) {
var user = this.userList[aUsername];
var totalUnread = aData.totalunread;
this.logItem(" - inboxUnread " + aData.inboxunread + " and this.userList[aUsername].inboxUnread is " + user.inboxUnread);
user.inboxNew = aData.inboxunread - user.inboxUnread;
user.inboxUnread = aData.inboxunread;
// XXX: do we really need this?
if (user.inboxNew < 0) {
user.inboxNew = 0;
}
// folders
// clear the array
user.folders = new Array();
user.folders.push(this.createFolder("inbox", aData.inboxunread));
for (var i = 0; i < aData.labels.length; i++) {
// gmail skins extension workaround
var skip = (aData.labels[i].name.indexOf("gmskin%3A") == 0);
if (!skip) {
user.folders.push(this.createFolder(aData.labels[i].name, aData.labels[i].unread));
this.logItem(" - Folder found (name: " + aData.labels[i].name + " and unread emails: " + aData.labels[i].unread + ")");
}
}
// quota
user.space_used_mb = aData.quota.spaceused;
user.total_mb = aData.quota.totalmb;
user.space_used_percent = aData.quota.spaceusedpercent;
// new/unread mail check
this.logItem(" -inboxUnread: " + aData.inboxunread + ", unreadEmails: " + user.unreadEmails + ", inboxNew: "+user.inboxNew+", totalunread: " + totalUnread);
var showInboxOnly = this.getPrefBranch().getBoolPref("gm-notifier.ui.counter.showInbox");
var hasNewMail = (totalUnread > user.unreadEmails);
if (!hasNewMail && showInboxOnly) {
hasNewMail = (user.inboxNew > 0);
}
if ((user.unreadEmails != null) && hasNewMail) {
this.logItem(" New Unread Mail Found!");
// new email
user.newEmails = totalUnread - user.unreadEmails;
user.unreadEmails = totalUnread;
if (showInboxOnly && (user.inboxNew == 0)) {
// show inbox only, but new mail wasn't in inbox
this.pushStateChange(aUsername, nsINotifierProgressListener.NO_NEW_MAIL);
//this.setNewMailMode(aUsername, false);
} else {
this.pushStateChange(aUsername, nsINotifierProgressListener.NEW_MAIL);
this.setNewMailMode(aUsername, true);
var count = this.getNewCount(aUsername);
this.newMailNotification(aUsername, count);
this.newMailCount = count;
}
} else if (user.unreadEmails == null && totalUnread > 0) {
// first time
this.logItem(" New Mail Found!");
user.newEmails = totalUnread;
user.unreadEmails = totalUnread;
if (showInboxOnly && (user.inboxNew == 0)) {
// show inbox only, but new mail wasn't in inbox
this.pushStateChange(aUsername, nsINotifierProgressListener.NO_NEW_MAIL);
} else {
this.pushStateChange(aUsername, nsINotifierProgressListener.NEW_MAIL);
this.setNewMailMode(aUsername, true);
this.logItem(" - amount of new mail is " + user.newEmails);
var count = this.getDisplayCount(aUsername);
this.logItem(" - mail notification about to be set, new mail count is " + count);
this.newMailNotification(aUsername, count);
this.newMailCount = count;
}
} else {
// check if the count decreased
var decreased = false;
if (totalUnread < user.unreadEmails) {
decreased = true;
}
user.unreadEmails = totalUnread;
this.logItem(" No New Unread Mail Found!");
this.pushStateChange(aUsername, nsINotifierProgressListener.NO_NEW_MAIL);
// only set mode to false if the count decreased. So if the user hasn't
// checked the mail and we had new mail before, we stay in the new mail
// mode.
if (decreased) {
user.newEmails = 0;
this.setNewMailMode(aUsername, false);
} else if (this.getNewMailMode(aUsername) &&
this.getPrefBranch().getBoolPref("gm-notifier.notification.repeat")) {
this.logItem(" - mail notification about to be set again, new mail count is " + this.newMailCount);
this.newMailNotification(aUsername, this.newMailCount);
} else {
user.newEmails = 0;
}
}
}
nsNotifierService.prototype.accountCheckSuccess = function(aUsername) {
if (!this.multiuser) {
// if we are in single user mode, set the user to be the default
var defaultUser = this.getPrefBranch().getCharPref("gm-notifier.users.default");
if (defaultUser != aUsername) {
this.getPrefBranch().setCharPref("gm-notifier.users.default", aUsername);
}
}
// if we aren't logged in, we are now and tell the world
if (!this.loggedIn) {
// update the pref
this.getPrefBranch().setBoolPref("gm-notifier.loggedin", true);
this.loggedIn = true;
this.pushStateChange(null, nsINotifierProgressListener.NOTIFIER_LOGGED_IN);
}
// success!
this.accountCheckResult(aUsername, nsINotifierProgressListener.LOGIN_SUCCESS);
}
nsNotifierService.prototype.buildCookieString = function(aCookieObject, aURI) {
var cookiestring = "";
for (var cookie in aCookieObject) {
if (aCookieObject[cookie]) {
// make sure the cookie fits the domain
//this.logItem("c: "+cookie + " " + aURI.host + " - " + aCookieObject[cookie].domain);
if (aURI.host == aCookieObject[cookie].domain || aURI.host.indexOf(aCookieObject[cookie].domain) >= 0) {
if (aCookieObject[cookie].value) {
cookiestring += aCookieObject[cookie].value + "; ";
}
}
}
}
//this.logItem("c2: "+cookiestring);
/*if (aCookieObject.GX) {
cookie += aCookieObject.GX + " ";
}
if (aCookieObject.SID) {
cookie += aCookieObject.SID + " ";
}
if (aCookieObject.LSID) {
cookie += aCookieObject.LSID + " ";
}
if (aCookieObject.HID) {
cookie += aCookieObject.HID + " ";
}
if (aCookieObject.GXAS) {
cookie += aCookieObject.GXAS + " ";
}
if (aCookieObject.GXAS_SEC) {
cookie += aCookieObject.GXAS_SEC + " ";
}
if (aCookieObject.S) {
cookie += aCookieObject.S + " ";
}*/
return cookiestring;
}
nsNotifierService.prototype.clearCookieObject = function(aUsername) {
for (var cookie in this.userList[aUsername].cookieObject) {
delete this.userList[aUsername].cookieObject[cookie];
}
/*this.userList[aUsername].cookieObject.GX = "";
this.userList[aUsername].cookieObject.SID = "";
this.userList[aUsername].cookieObject.LSID = "";
this.userList[aUsername].cookieObject.GXAS_SEC = "";
this.userList[aUsername].cookieObject.GXAS = "";
this.userList[aUsername].cookieObject.HID = "";
this.userList[aUsername].cookieObject.S = "";*/
delete this.userList[aUsername].rememberme;
delete this.userList[aUsername].remembermeExpires;
}
nsNotifierService.prototype.loadCookieData = function(aUsername) {
// We should follow the user's choice if to remember the login info beyond
// the current session.
this.logItem(" -- loading cookie data for " + aUsername);
var rememberme = this.userList[aUsername].rememberme;
var remembermeExpires = this.userList[aUsername].remembermeExpires;
var isHostedDomain = this.isHostedDomain(aUsername);
if (rememberme == undefined) {
var cookieMgr = Components.classes[kCOOKIESERVICE_CONTRACTID]
.getService(Components.interfaces.nsICookieManager);
var e = cookieMgr.enumerator;
var cookie, done = false;
while (!done && e.hasMoreElements()) {
cookie = e.getNext().QueryInterface(Components.interfaces.nsICookie);
if (cookie.host == ".google.com" || cookie.host == "google.com") {
if (!isHostedDomain && cookie.name == "rememberme") {
done = true;
rememberme = (cookie.value == "true");
if (rememberme) {
remembermeExpires = cookie.expires;
} else {
remembermeExpires = null;
}
this.logItem(" -- 1 rememberme cookie value is " + cookie.value);
} else if (!isHostedDomain && cookie.name == "LSID" &&
(rememberme == undefined)) {
// no remember me, check expire date
var expireDate = new Date(1000 * cookie.expires);
var currentDate = new Date();
this.logItem(" "+expireDate.getFullYear() + " vs " + currentDate.getFullYear());
if ((expireDate.getFullYear() - 2) > currentDate.getFullYear()) {
// expires in more than 2 years
rememberme = true;
remembermeExpires = cookie.expires;
this.logItem(" -- 2 rememberme cookie value is " + cookie.value);
}
} else if (isHostedDomain && cookie.name == "HID") {
if (cookie.path != "/hosted/" + this.getHostedDomain(aUsername) + "/") {
continue;
}
// we are done
done = true;
// no remember me, check expire date
var expireDate = new Date(1000 * cookie.expires);
var currentDate = new Date();
this.logItem(" "+expireDate.getFullYear() + " vs " + currentDate.getFullYear());
if ((expireDate.getFullYear() - 2) > currentDate.getFullYear()) {
// expires in more than 2 years
rememberme = true;
remembermeExpires = cookie.expires;
this.logItem(" -- 3 rememberme cookie value is " + cookie.value);
}
}
}
}
if (done) {
// store the rememberme
this.userList[aUsername].rememberme = rememberme;
this.userList[aUsername].remembermeExpires = remembermeExpires;
}
}
this.logItem(" remembermeExpires: "+this.userList[aUsername].remembermeExpires);
var cookieService = Components.classes["@mozilla.org/cookieService;1"]
.getService(Components.interfaces.nsICookieService);
var ioService = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
function formatExpiresString(aExpires) {
if (!aExpires) {
return "0";
}
// stolen from the cookie prefs in Mozilla :)
var sdf = Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
.getService(Components.interfaces.nsIScriptableDateFormat);
var date = new Date(1000 * aExpires);
return sdf.FormatDateTime("", sdf.dateFormatLong,
sdf.timeFormatSeconds,
date.getFullYear(),
date.getMonth() + 1,
date.getDate(),
date.getHours(),
date.getMinutes(),
date.getSeconds());
}
var expires = "";
if (typeof(this.userList[aUsername].remembermeExpires) == "string") {
expires = "Expires=" + formatExpiresString(this.userList[aUsername].remembermeExpires);
}
this.clearCookieData();
// XXX: why not just load all cookies from the cookie object
if (!isHostedDomain) {
var gx = this.userList[aUsername].cookieObject.GX.value + ";";
if (!!expires) {
gx += expires+";";
}
gx += "Path=/mail";
//this.loadCookieIntoApp(this.userList[aUsername].cookieObject.GX);
//cookieService.setCookieString(ioService.newURI("http://mail.google.com", null, null), null, gx, null);
//this.loadCookieIntoApp(this.userList[aUsername].cookieObject.SID);
//cookieService.setCookieString(ioService.newURI("http://.google.com", null, null), null, this.userList[aUsername].cookieObject.SID.value + ";" + expires, null);
//this.loadCookieIntoApp(this.userList[aUsername].cookieObject.GXSP);
for (var x in this.userList[aUsername].cookieObject) {
this.loadCookieIntoApp(this.userList[aUsername].cookieObject[x]);
}
} else {
if (this.userList[aUsername].cookieObject.GXAS_SEC) {
this.loadCookieIntoApp(this.userList[aUsername].cookieObject.GXAS_SEC);
//cookieService.setCookieString(ioService.newURI("http://mail.google.com", null, null), null, this.userList[aUsername].cookieObject.GXAS_SEC.value + ";" + expires+";Path=/a", null);
}
this.loadCookieIntoApp(this.userList[aUsername].cookieObject.GXSP);
if (this.userList[aUsername].cookieObject.GXAS) {
this.loadCookieIntoApp(this.userList[aUsername].cookieObject.GXAS);
//cookieService.setCookieString(ioService.newURI("http://mail.google.com", null, null), null, this.userList[aUsername].cookieObject.GXAS.value + ";" + expires+";Path=/a", null);
}
if (this.userList[aUsername].cookieObject.HID) {
this.loadCookieIntoApp(this.userList[aUsername].cookieObject.HID);
//cookieService.setCookieString(ioService.newURI("https://.www.google.com", null, null), null, this.userList[aUsername].cookieObject.HID.value + ";" + expires+";Path=/a/"+this.getHostedDomain(aUsername)+"/", null);
}
if (this.userList[aUsername].isGmail20) {
this.loadCookieIntoApp(this.userList[aUsername].cookieObject.S);
//cookieService.setCookieString(ioService.newURI("http://mail.google.com", null, null), null, this.userList[aUsername].cookieObject.S.value + ";" + expires+";Path=/a/"+this.getHostedDomain(aUsername), null);
}
}
}
nsNotifierService.prototype.clearCookieData = function() {
this.logItem(" -- clearing cookie data!!");
var cookieMgr = Components.classes[kCOOKIESERVICE_CONTRACTID]
.getService(Components.interfaces.nsICookieManager);
var e = cookieMgr.enumerator;
while (e.hasMoreElements()) {
var cookie = e.getNext().QueryInterface(Components.interfaces.nsICookie);
//this.logItem(cookie.host+ " , "+cookie.name+ " , " +cookie.path);
if (cookie.host == "mail.google.com" || cookie.host == ".mail.google.com") {
//this.logItem(cookie.host+ " , "+cookie.name+ " , " +cookie.path);
cookieMgr.remove(cookie.host, cookie.name, cookie.path, false);
}
}
// cookieMgr.remove("mail.google.com", "GX", "/mail", false);
//cookieMgr.remove(".google.com", "SID", "/", false);
//cookieMgr.remove("mail.google.com", "GXAS", "/hosted", false);
//cookieMgr.remove("mail.google.com", "GXAS", "/a/", false);
//cookieMgr.remove("mail.google.com", "GXSP", "", false);
//cookieMgr.remove("mail.google.com", "GXAS_SEC", "/hosted", false);
//cookieMgr.remove("mail.google.com", "S", "/mail", false);
var defaultUser = this.getPrefBranch().getCharPref("gm-notifier.users.default");
cookieMgr.remove(".google.com", "HID", "/hosted"+this.getHostedDomain(defaultUser)+"/", false);
}
nsNotifierService.prototype.isHostedDomain = function(aUsername) {
var isHosted = false;
if (aUsername.indexOf("@") > 0) {
var domain = this.getHostedDomain(aUsername);
// don't treat gmail.com/googlemail.com has hosted domains
if (domain == "gmail.com" || domain == "googlemail.com") {
isHosted = false;
} else {
isHosted = true;
}
}
return isHosted;
}
nsNotifierService.prototype.getHostedDomain = function(aUsername) {
var index = aUsername.indexOf("@");
if (index >= 0) {
return aUsername.substring(index+1, aUsername.length).toLowerCase();
} else {
return null;
}
}
/* json code */
/* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla code.
*
* The Initial Developer of the Original Code is
* Simon Bⁿnzli <zeniko@gmail.com>
* Portions created by the Initial Developer are Copyright (C) 2006-2007
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
/**
* Utilities for JavaScript code to handle JSON content.
* See http://www.json.org/ for comprehensive information about JSON.
*
* Import this module through
*
* Components.utils.import("resource://gre/modules/JSON.jsm");
*
* Usage:
*
* var newJSONString = JSON.toString( GIVEN_JAVASCRIPT_OBJECT );
* var newJavaScriptObject = JSON.fromString( GIVEN_JSON_STRING );
*
* Note: For your own safety, Objects/Arrays returned by
* JSON.fromString aren't instanceof Object/Array.
*/
var JSON = {
/**
* Converts a JavaScript object into a JSON string.
*
* @param aJSObject is the object to be converted
* @param aKeysToDrop is an optional array of keys which will be
* ignored in all objects during the serialization
* @return the object's JSON representation
*
* Note: aJSObject MUST not contain cyclic references.
*/
toString: function JSON_toString(aJSObject, aKeysToDrop) {
// we use a single string builder for efficiency reasons
var pieces = [];
// this recursive function walks through all objects and appends their
// JSON representation (in one or several pieces) to the string builder
function append_piece(aObj) {
if (typeof aObj == "string") {
aObj = aObj.replace(/[\\"\x00-\x1F\u0080-\uFFFF]/g, function($0) {
// use the special escape notation if one exists, otherwise
// produce a general unicode escape sequence
switch ($0) {
case "\b": return "\\b";
case "\t": return "\\t";
case "\n": return "\\n";
case "\f": return "\\f";
case "\r": return "\\r";
case '"': return '\\"';
case "\\": return "\\\\";
}
return "\\u" + ("0000" + $0.charCodeAt(0).toString(16)).slice(-4);
});
pieces.push('"' + aObj + '"')
}
else if (typeof aObj == "boolean") {
pieces.push(aObj ? "true" : "false");
}
else if (typeof aObj == "number" && isFinite(aObj)) {
// there is no representation for infinite numbers or for NaN!
pieces.push(aObj.toString());
}
else if (aObj === null) {
pieces.push("null");
}
// if it looks like an array, treat it as such - this is required
// for all arrays from either outside this module or a sandbox
else if (aObj instanceof Array ||
typeof aObj == "object" && "length" in aObj &&
(aObj.length === 0 || aObj[aObj.length - 1] !== undefined)) {
pieces.push("[");
for (var i = 0; i < aObj.length; i++) {
arguments.callee(aObj[i]);
pieces.push(",");
}
if (aObj.length > 0)
pieces.pop(); // drop the trailing colon
pieces.push("]");
}
else if (typeof aObj == "object") {
pieces.push("{");
for (var key in aObj) {
// allow callers to pass objects containing private data which
// they don't want the JSON string to contain (so they don't
// have to manually pre-process the object)
if (aKeysToDrop && aKeysToDrop.indexOf(key) != -1)
continue;
arguments.callee(key.toString());
pieces.push(":");
arguments.callee(aObj[key]);
pieces.push(",");
}
if (pieces[pieces.length - 1] == ",")
pieces.pop(); // drop the trailing colon
pieces.push("}");
}
else {
throw new TypeError("No JSON representation for this object!");
}
}
append_piece(aJSObject);
return pieces.join("");
},
/**
* Converts a JSON string into a JavaScript object.
*
* @param aJSONString is the string to be converted
* @return a JavaScript object for the given JSON representation
*/
fromString: function JSON_fromString(aJSONString) {
if (!this.isMostlyHarmless(aJSONString))
throw new SyntaxError("No valid JSON string!");
var s = new Components.utils.Sandbox("about:blank");
return Components.utils.evalInSandbox("(" + aJSONString + ")", s);
},
/**
* Checks whether the given string contains potentially harmful
* content which might be executed during its evaluation
* (no parser, thus not 100% safe! Best to use a Sandbox for evaluation)
*
* @param aString is the string to be tested
* @return a boolean
*/
isMostlyHarmless: function JSON_isMostlyHarmless(aString) {
const maybeHarmful = /[^,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]/;
const jsonStrings = /"(\\.|[^"\\\n\r])*"/g;
return !maybeHarmful.test(aString.replace(jsonStrings, ""));
}
};
// timing stuff
nsNotifierService.prototype.setTimer = function() {
if (this.updateTimer) {
this.updateTimer.cancel();
} else {
this.updateTimer = Components.classes[kTIMER_CONTRACTID].createInstance(nsITimer);
}
this.logItem("Starting at : " + new Date());
try {
this.updateTimer.initWithCallback(this, this.timeOut, nsITimer.TYPE_ONE_SHOT);
} catch (e) {
this.logItem(e);
}
}
// nsITimer
nsNotifierService.prototype.notify = function(aTimer) {
this.logItem(" *************** notify called");
this.logItem("End: " + new Date());
this.checkAccounts();
}
nsNotifierService.prototype.pushStateChange = function(aUsername, aState) {
for (var run = 0; run < this.listeners.length; run++) {
// can be null if a listener was removed
if (this.listeners[run]) {
this.listeners[run].onStateChange(aUsername, aState);
}
}
}
nsNotifierService.prototype.observer = function(aCallbackFunc, aUsername, aThis) {
var myThis = aThis;
return ({
data : "",
onStartRequest : function (aRequest, aContext) {
this.data = "";
},
onDataAvailable : function (aRequest, aContext, aStream, aSourceOffset, aLength){
var scriptableInputStream =
Components.classes["@mozilla.org/scriptableinputstream;1"]
.createInstance(Components.interfaces.nsIScriptableInputStream);
scriptableInputStream.init(aStream);
this.data += scriptableInputStream.read(aLength);
},
onStopRequest : function (aRequest, aContext, aStatus) {
// XXX: proxy issue: aStatus is an error from http://lxr.mozilla.org/seamonkey/source/netwerk/base/public/nsNetError.h#172
aCallbackFunc(this.data, aRequest, aUsername);
},
onChannelRedirect : function (aOldChannel, aNewChannel, aFlags) {
//dump("\nredirect!");
if (aOldChannel == myThis.channel) {
//myThis.logItem(" - redirect to: "+aNewChannel.URI.spec);
myThis.channel = aNewChannel;
}
},
// nsIInterfaceRequestor
getInterface: function (aIID) {
try {
return this.QueryInterface(aIID);
} catch (e) {
throw Components.results.NS_NOINTERFACE;
}
},
// nsIProgressEventSink (to shut up annoying debug exceptions
onProgress : function (aRequest, aContext, aProgress, aProgressMax) { },
onStatus : function (aRequest, aContext, aStatus, aStatusArg) { },
// nsIHttpEventSink (to shut up annoying debug exceptions
onRedirect : function (aOldChannel, aNewChannel) { },
QueryInterface : function(aIID) {
if (aIID.equals(nsISupports) ||
aIID.equals(Components.interfaces.nsIDocShell) ||
aIID.equals(Components.interfaces.nsIInterfaceRequestor) ||
aIID.equals(Components.interfaces.nsIChannelEventSink) ||
aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
aIID.equals(Components.interfaces.nsIProgressEventSink) ||
aIID.equals(Components.interfaces.nsIPrompt) ||
aIID.equals(Components.interfaces.nsIHttpEventSink) ||
aIID.equals(Components.interfaces.nsIDocShellTreeItem) ||
aIID.equals(Components.interfaces.nsIStreamListener))
return this;
throw Components.results.NS_NOINTERFACE;
}
}
);
}
/*
New mail notification
*/
nsNotifierService.prototype.newMailNotification = function(aUsername, aNewNum) {
if (aNewNum < 1) {
return;
}
this.logItem(" New Mail Notification Init:");
var isNotificationEnabled = this.getPrefBranch().getBoolPref("gm-notifier.ui.notification.enabled");
this.logItem(" Is System Notification Enabled by user: " + isNotificationEnabled);
if (isNotificationEnabled) {
var msg = this.getFormattedString("NotificationMsg", [aNewNum]);
var title = this.getFormattedString("NotificationMsgTitle", [aUsername]);
try {
if ("@mozilla.org/alerts-service;1" in Components.classes) {
var alertService = Components.classes["@mozilla.org/alerts-service;1"]
.getService(Components.interfaces.nsIAlertsService);
if (alertService) {
alertService.showAlertNotification("chrome://gm-notifier/content/gm-logo.png",
title, msg, true, aUsername, this);
this.logItem(" alertsService success.");
} else {
this.logItem(" alertsService failure: could not getService nsIAlertsService");
}
} else if ("@growl.info/notifications;1" in Components.classes) {
// try growl
var growl = Components.classes["@growl.info/notifications;1"].getService(Components.interfaces.grINotifications);
if (growl) {
this.logItem(" using growl service");
growl.sendNotification(aUsername, "chrome://gm-notifier/content/gm-logo.png", title, msg, this);
} else {
this.logItem(" growl service failed");
}
}
} catch(e) {
this.logItem(" alertsService failure: " + e);
}
}
// sound notifications
var isSoundNotificationsEnabled = this.getPrefBranch().getBoolPref("gm-notifier.ui.soundnotification.enabled");
this.logItem(" Is Sound Notification Enabled by user: " + isSoundNotificationsEnabled);
if (isSoundNotificationsEnabled) {
var soundUrl = this.getPrefBranch().getCharPref("gm-notifier.ui.soundnotification.uri");
try {
var sound = Components.classes["@mozilla.org/sound;1"]
.createInstance(Components.interfaces.nsISound);
sound.init();
var shortname = soundUrl.substr(0, 7);
if (shortname == "http://" || shortname == "https:/") {
var url = Components.classes["@mozilla.org/network/standard-url;1"]
.createInstance(Components.interfaces.nsIURL);
url.spec = soundUrl;
sound.play(url);
} else {
var localFile = Components.classes["@mozilla.org/file/local;1"]
.createInstance(Components.interfaces.nsILocalFile);
localFile.initWithPath(soundUrl);
var ios = Components.classes["@mozilla.org/network/io-service;1"]
.getService(Components.interfaces.nsIIOService);
sound.play(ios.newFileURI(localFile));
}
} catch (e) {
this.logItem(" Sound playing failed with: "+ e);
}
}
}
// nsIObserver for alert service - aData will be the username
nsNotifierService.prototype.observe = function (aSubject, aTopic, aData){
switch (aTopic) {
case "alertfinished":
break;
case "alertclickcallback":
this.showNotification(aData);
break;
case "http-on-modify-request":
// happens right before we send out the request, so that we can overwrite
// the cookie data with ours. Make sure it is our connection first.
if (aSubject == this.channel) {
var httpChannel = aSubject.QueryInterface(nsIHttpChannel);
// overwrite the cookie we are going to be sending with ours.
var username = this.userQueue[this.currentUserQueue];
// create cookie string
var cookieobject = this.userList[username].cookieObject;
var cookie = this.buildCookieString(cookieobject, httpChannel.URI);
this.logItem(" -- cookie string is: " + cookie);
httpChannel.setRequestHeader("Cookie", cookie, false);
//dump("\n Request: "+ httpChannel.URI.spec+ "\n Cookie: "+ cookie +"\n");
}
break;
case "http-on-examine-response":
// happens right before we process the response, so that we can store
// and empty the Set-Cookie directive, so that the user's cookies aren't
// overwritten by our connections. Make sure it is our connection first.
if (aSubject == this.channel) {
var httpChannel = aSubject.QueryInterface(nsIHttpChannel);
var cookie = "";
try {
cookie = httpChannel.getResponseHeader("Set-Cookie");
} catch(e) {}
//dump("\n Response: " + httpChannel.URI.spec+ "\n Cookie: " + cookie +"\n");
var username = this.userQueue[this.currentUserQueue];
// parse the cookie. Yes, we have a mini-parser here to make sure we get
// all the cookies we need and dump the unused ones (including the
// commands to expire certain cookies.
this.logItem(" -- cookie response is: " + cookie);
var split = cookie.split("\n");
var newcookie = "";
var usercookieobject = this.userList[username].cookieObject;
var expired = false;
function getCookieString(aString) {
var end = aString.indexOf(";");
return aString.substr(0, end);
}
function getCookieDomain(aString) {
var domain = aString.match(/\Domain=([a-zA-Z0-9.]*);/);
return domain ? domain[1] : httpChannel.URI.host;
}
function getCookiePath(aString) {
var path = aString.match(/\Path=([a-zA-Z0-9.\/]*);/);
return path ? path[1] : null;
}
for (var i = 0; i < split.length; i++) {
if (split[i].indexOf("=EXPIRED") > 0) {
expired = true;
} else {
expired = false;
}
var index = split[i].indexOf("=");
var cookiename = split[i].substr(0, index);
if (!cookiename) {
continue;
}
if (expired) {
//dump("\n -- killing "+cookiename);
delete usercookieobject[cookiename];
} else {
usercookieobject[cookiename] = {name: cookiename, value: getCookieString(split[i]), domain: getCookieDomain(split[i]), path: getCookiePath(split[i])};
//usercookieobject[cookiename] = {value: getCookieString(split[i]), domain: getCookieDomain(split[i]), path: getCookiePath(split[i])};
//dump("\n -- setting "+cookiename +" domain: "+usercookieobject[cookiename].domain+" path:"+usercookieobject[cookiename].path);
}
//dump(split[i]);
// XXX: gmail specific stuff here
/*if (split[i].indexOf("GX=") > -1) {
if (expired) {
usercookieobject.GX = "";
} else {
newcookie = split[i].match(/\GX=[a-zA-Z0-9_-]*;/);
usercookieobject.GX = newcookie;
}
} else if (split[i].indexOf("LSID=") > -1) {
if (expired) {
usercookieobject.LSID = "";
} else {
newcookie = getCookieString(split[i]);
usercookieobject.LSID = newcookie;
}
} else if (split[i].indexOf("SID=") > -1) {
if (expired) {
usercookieobject.SID = "";
} else {
newcookie = split[i].match(/\SID=[a-zA-Z0-9_-]*;/);
usercookieobject.SID = newcookie;
}
} else if (split[i].indexOf("HID=") > -1) {
if (expired) {
// XXX: we need HID when you load hosted webmail, but why
// is it being removed?
//usercookieobject.HID = "";
} else {
newcookie = split[i].match(/\HID=[a-zA-Z0-9_-]*;/);
usercookieobject.HID = newcookie;
}
} else if (split[i].indexOf("GXAS=") > -1) {
if (expired) {
usercookieobject.GXAS = "";
} else {
newcookie = split[i].match(/\GXAS=[a-zA-Z0-9_.=-]*;/);
usercookieobject.GXAS = getCookieString(split[i]);
}
} else if (split[i].indexOf("GXAS_SEC=") > -1) {
if (expired) {
usercookieobject.GXAS_SEC = "";
} else {
newcookie = split[i].match(/\GXAS_SEC=[a-zA-Z0-9_.=-]*;/);
usercookieobject.GXAS_SEC = getCookieString(split[i]);
}
} else if (split[i].indexOf("S=") == 0) {
if (expired) {
usercookieobject.S = "";
} else {
newcookie = getCookieString(split[i]);
//dump("\n\nnew cookie: |"+newcookie+"|\n\n");
usercookieobject.S = newcookie;
}
}*/
}
// make sure we don't store the Set-Cookie directive by making it empty
httpChannel.setResponseHeader("Set-Cookie", "", false);
}
break;
}
}
nsNotifierService.prototype.showNotification = function(aUsername) {
// when the alert callback happens, we want to notify the listener that
// initiated the server connection that the alert was clicked.
// Simply pushing a state change to all listeners will result in the
// infinite window open loop!
this.logItem("Alert Clickback called for " + aUsername + ":");
this.logItem(" Notification Window is: (" + this.notificationListenerID + ").");
var run = 0;
if (this.notificationListenerID) {
var done = false;
while (!done && (run < this.listeners.length)) {
this.logItem(" Item " + run + " has id (" + this.listeners[run].getID() + ")");
if (this.listeners[run] && (this.listeners[run].getID() == this.notificationListenerID))
done = true;
else
run++;
}
} else {
// fallback
while ((this.listeners[run] == null) && (run < this.listeners.length))
run++;
this.logItem(" No notification window found, call listener #" + run);
}
if (this.listeners[run]) {
this.listeners[run].onStateChange(aUsername, nsINotifierProgressListener.LOAD_MAIL);
}
}
nsNotifierService.prototype.createFolder = function(aName, aUnreadMail) {
return {
name: aName,
unreadMail: aUnreadMail
}
}
/* Pref code */
nsNotifierService.prototype.getPrefBranch = function(){
if (!this.prefBranch) {
this.prefBranch = Components.classes['@mozilla.org/preferences-service;1'];
this.prefBranch = this.prefBranch.getService();
this.prefBranch = this.prefBranch.QueryInterface(Components.interfaces.nsIPrefBranch);
}
return this.prefBranch;
}
nsNotifierService.prototype.prefChanged = function(aPrefName) {
switch (aPrefName) {
case "gm-notifier.update.interval":
this.setTimeout(this.getPrefBranch().getIntPref("gm-notifier.update.interval"));
break;
case "gm-notifier.multiaccount.enabled":
if (!this.supportsMultiMode()) {
this.multiuser = false;
} else {
this.multiuser = this.getPrefBranch().getBoolPref("gm-notifier.multiaccount.enabled");
}
this.buildUserQueue();
if (!this.multiuser) {
// build a new user list
// log out every user other than the default user
for (name in this.userList) {
if (name != this.defaultUser) {
this.logoutUser(name);
}
}
}
break;
case "gm-notifier.users.default":
try {
this.defaultUser = this.getPrefBranch().getCharPref("gm-notifier.users.default");
} catch (e) {}
break;
}
}
nsNotifierService.prototype.addPrefObserver = function(aDomain, aFunction){
var myPrefs = this.getPrefBranch();
var prefBranchInternal = myPrefs.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
if (prefBranchInternal) {
prefBranchInternal.addObserver(aDomain, aFunction, false);
}
}
/* Bundle code */
nsNotifierService.prototype.getStringBundle = function() {
if (!this.stringBundle) {
var strBundleService = Components.classes["@mozilla.org/intl/stringbundle;1"]
.createInstance(Components.interfaces.nsIStringBundleService);
this.stringBundle = strBundleService.createBundle("chrome://gm-notifier/locale/gm-notifier.properties");
}
return this.stringBundle;
}
nsNotifierService.prototype.getString = function(aName) {
return this.getStringBundle().GetStringFromName(aName);
}
nsNotifierService.prototype.getFormattedString = function(aName, aStrArray) {
return this.getStringBundle().formatStringFromName(aName, aStrArray, aStrArray.length);
}
nsNotifierService.prototype.getLoginDetails = function(aUsername) {
var url = "chrome://gm-notifier/";
// check for toolkit's login manager (Mozilla 1.9)
if (Components.classes[kLOGINMANAGER_CONTRACTID]) {
var passwordManager = Components.classes[kLOGINMANAGER_CONTRACTID].getService(nsILoginManager);
var logins = passwordManager.findLogins({}, url, null, "gm-notifier");
for (var i = 0; i < logins.length; i++) {
if (logins[i].username == aUsername) {
var password = logins[i].password;
if (password === " ") {
// XXX: empty password is " " for now due to ff3 change
return "";
} else {
return password;
}
}
}
} else {
var passwordManager = Components.classes[kPSWDMANAGER_CONTRACTID]
.createInstance(nsIPasswordManagerInternal);
var host = {value:""};
var user = {value:""};
var password = {value:""};
try {
passwordManager.findPasswordEntry(url, aUsername, "", host, user, password);
} catch(e){ }
return password.value;
}
}
nsNotifierService.prototype.getPassword = function(aUsername) {
var password = "";
if (this.userList[aUsername]) {
password = this.userList[aUsername].password;
}
if (!password) {
password = this.getLoginDetails(aUsername);
}
return password;
}
nsNotifierService.prototype.getURLProtocol = function() {
/*var urlprotocol = "";
if (this.getPrefBranch().getBoolPref("gm-notifier.connection.use.unsecured")) {
urlprotocol = "http://";
} else {
urlprotocol = "https://";
}
return urlprotocol;*/
return "https://";
}
nsNotifierService.prototype.supportsMultiMode = function() {
var supports = false;
try {
var appInfo = Components.classes["@mozilla.org/xre/app-info;1"]
.getService(Components.interfaces.nsIXULAppInfo);
if (appInfo.platformVersion >= "1.8.1") {
supports = true;
}
} catch (e) {}
return supports;
}
nsNotifierService.prototype.newUserListItem = function(aUsername, aPassword) {
if (!this.userList[aUsername]) {
this.userCount++;
}
return {
name: aUsername,
password: aPassword,
resetState: false,
newMailMode: false,
folders: new Array(),
unreadEmails: null,
tmpUnreadEmails: null,
newEmails: 0,
space_used_mb: null,
space_used_percent: null,
total_mb: null,
inboxUnread: 0,
inboxNew: 0,
state: nsINotifierService.USER_STATE_LOGGED_OUT,
cookieObject: new Object()
}
}
nsNotifierService.prototype.hitch = function(aScope, aFunction) {
function toArray(aObj) {
var arr = [];
for (var i = 0; i < aObj.length; i++) {
arr.push(aObj[i]);
}
return arr;
}
var origArgs = toArray(arguments);
origArgs.splice(0, 2);
return function() {
var newArgs = toArray(arguments);
// add origArgs to end
for (var i = 0; i < origArgs.length; i++) {
newArgs.push(origArgs[i]);
}
aFunction.apply(aScope, newArgs)
}
}
/**
* JS XPCOM component registration goop:
*
* We set ourselves up to observe the xpcom-startup category. This provides
* us with a starting point.
*/
nsNotifierService.prototype.QueryInterface = function(iid) {
if (!iid.equals(nsINotifierService) &&
!iid.equals(Components.interfaces.nsIObserver) &&
!iid.equals(nsISupports))
throw Components.results.NS_ERROR_NO_INTERFACE;
return this;
}
var nsNotifierServiceModule = new Object();
nsNotifierServiceModule.registerSelf = function (compMgr, fileSpec, location, type) {
compMgr = compMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar);
compMgr.registerFactoryLocation(kGMSERVICE_CID,
"nsIGMNotifierService",
kGMSERVICE_CONTRACTID,
fileSpec,
location,
type);
}
nsNotifierServiceModule.getClassObject = function (compMgr, cid, iid) {
if (!cid.equals(kGMSERVICE_CID)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
if (!iid.equals(Components.interfaces.nsIFactory)) {
throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
}
return nsNotifierServiceFactory;
}
nsNotifierServiceModule.canUnload = function (compMgr) {
// cleanup
this.listeners = null;
if (this.updateTimer) {
this.updateTimer.cancel();
}
this.updateTimer = null;
this.observerService = null;
return true;
}
var nsNotifierServiceFactory = new Object();
nsNotifierServiceFactory.createInstance = function (outer, iid) {
if (outer != null) {
throw Components.results.NS_ERROR_NO_AGGREGATION;
}
if (!iid.equals(nsINotifierService) && !iid.equals(nsISupports)) {
throw Components.results.NS_ERROR_NO_INTERFACE;
}
return new nsNotifierService();
}
function NSGetModule(compMgr, fileSpec) {
return nsNotifierServiceModule;
}